#light

module CGraph.IO

open System.IO
open System.Text
open System


//You create data writers upon byte streams w/ encoding, then you compose the byte streams.
type IDataWriter = 
    abstract Strings : value:string array-> unit
    abstract Doubles : value:float array-> unit
    abstract Ints : value:int array-> unit
    abstract Bytes : value:byte array-> unit
    interface IDisposable
    
//Readers are more complex to support lazy evaluation.  
type IDataReader =
    abstract Strings : unit->string seq
    abstract Doubles : unit->float seq
    abstract Ints : unit->int seq
    abstract Bytes : unit -> byte array
    interface IDisposable
    
type IODataType =
    | Unknown = 0
    | IntArray = 1
    | DoubleArray = 2
    | StringArray = 3
    | ByteArray = 4
    
let Encoding = Encoding.Unicode

let CheckedRead (stream:Stream) bytes offset count =
    let check = stream.Read(bytes, offset, count)
    if ( check <> count ) then failwith "Failed to read data"

let CheckedWrite (stream:Stream) bytes offset count = 
    stream.Write( bytes, offset, count )
    
    
let CreateDataReader (fstream:Stream) = 
    let (tempBytes:byte array) = Array.zero_create(4)
    let LCheckedRead = CheckedRead fstream tempBytes 0
    let readInt() =
        LCheckedRead 4
        BitConverter.ToInt32(tempBytes,0)
        
    let CheckType (exp:IODataType) =
        let (dtype:IODataType) = enum(readInt())
        if  dtype <> exp then failwith "Incorrect type detected; data corrupted"
        
    let readInts() =
        CheckType IODataType.IntArray
        let count = readInt()
        let (bytes:byte array) = Array.zero_create(count*4)
        CheckedRead fstream bytes 0 bytes.Length
        seq {
            for idx in 0..(count-1) do
                yield BitConverter.ToInt32( bytes, idx*4)
        }
        
    let readDoubles() =
        CheckType IODataType.DoubleArray
        let count = readInt()
        let (bytes:byte array) = Array.zero_create(count*8)
        CheckedRead fstream bytes 0 bytes.Length
        seq {
            for idx in 0..(count-1) do
                yield BitConverter.ToDouble(bytes,8*idx)
        }
    let readBytes() = 
        CheckType IODataType.ByteArray
        let count = readInt()
        let (bytes:byte array) = Array.zero_create(count);
        CheckedRead fstream bytes 0 count
        bytes
    let readStrings() = 
        CheckType IODataType.StringArray
        let bytes = readBytes()
        let byteLen = bytes.Length
        let dataPtr = ref 0
        let nextOffset size = 
            let retval = !dataPtr
            dataPtr := !dataPtr + size
            retval
        seq {
            while( !dataPtr < byteLen ) do
                let strSize = BitConverter.ToInt32(bytes, (nextOffset 4))
                let retval = Encoding.GetString( bytes, (nextOffset strSize), strSize )
                yield retval
        }
    { new IDataReader with
        member s.Strings() = readStrings()
        member s.Doubles() = readDoubles()
        member s.Ints() = readInts()
        member s.Bytes() = readBytes()
        member s.Dispose() = fstream.Dispose()
    }
    
let CreateDataWriter (fstream:Stream) = 
    let (tempBytes:byte[] ref) = ref (Array.zero_create(8));
    let LCheckedWrite (bytes:byte[]) = CheckedWrite fstream bytes 0 bytes.Length
        
    let writeDouble (data:float) = 
        let bytes = BitConverter.GetBytes(data)
        LCheckedWrite bytes
    let writeInt (data:int) = 
        let bytes = BitConverter.GetBytes(data)
        LCheckedWrite bytes
    let writeType (arg:IODataType) = 
        writeInt (int arg)
    let writeBytes (data:byte array) = 
        writeInt data.Length
        CheckedWrite fstream data 0 data.Length
        
    { new IDataWriter with
        member s.Strings data = 
            let bytes = System.Collections.Generic.List<byte>()
            data |> Seq.iter( fun item ->
                let count = Encoding.GetByteCount(item)
                let len = BitConverter.GetBytes(count)
                bytes.AddRange len
                if count >= (!tempBytes).Length
                    then tempBytes := Array.zero_create(2*count)
                Encoding.GetBytes(item,0,item.Length, !tempBytes, 0 ) |> ignore
                bytes.AddRange <| Array.sub !tempBytes 0 count )
            writeType IODataType.StringArray
            s.Bytes <| bytes.ToArray()
        member s.Doubles data = 
            writeType IODataType.DoubleArray
            writeInt data.Length
            data |> Seq.iter( fun item -> writeDouble item )
        member s.Ints data = 
            writeType IODataType.IntArray
            writeInt data.Length
            data |> Seq.iter( fun item -> writeInt item )
        member s.Bytes data = 
            writeType IODataType.ByteArray
            writeBytes data
        member s.Dispose() = fstream.Dispose()
    }